The most basic form of OSGi testing can be done with an OSGiFrameworkTest. This would boostrap the framework in the @BeforeClass scope and make the framework instance available through getFramework(). Due to classloading restrictions, you can only share primitive types between the test and the framework.
public class SimpleFrameworkTestCase extends OSGiFrameworkTest { @Test public void testSimpleBundle() throws Exception { // Get the bundle location URL url = getTestArchiveURL("example-simple.jar"); // Install the Bundle BundleContext sysContext = getFramework().getBundleContext(); Bundle bundle = sysContext.installBundle(url.toExternalForm()); assertBundleState(Bundle.INSTALLED, bundle.getState()); // Start the bundle bundle.start(); assertBundleState(Bundle.ACTIVE, bundle.getState()); // Stop the bundle bundle.stop(); assertBundleState(Bundle.RESOLVED, bundle.getState()); // Uninstall the bundle bundle.uninstall(); assertBundleState(Bundle.UNINSTALLED, bundle.getState()); } }
These tests always work with an embedded OSGi framework. You can use the -Dframework property to run the test against a different framework implemenation (i.e. Apache Felix ).
A common pattern in OSGi is that a bundle contains some piece of meta data that gets processed by some other infrastructure bundle that is installed in the OSGi Framework. In such cases the well known Extender Pattern is often being used. JBoss OSGi offeres a differnet approach to address this problem which is covered by the Extender Pattern vs. Lifecycle Interceptor post in the JBoss OSGi Diary .
Extending an OSGi Bundle
Extender registers itself as BundleListener
Bundle gets installed/started# Framework fires a BundleEvent
Extender picks up the BundleEvent (e.g. STARTING)
Extender reads metadata from the Bundle and does its work
There is no extender specific API. It is a pattern rather than a piece of functionality provided by the Framework. Typical examples of extenders are the Blueprint or Web Application Extender.
Client code that installs, starts and uses the registered endpoint could look like this.
// Install and start the Web Application bundle Bundle bundle = context.installBundle("mywebapp.war"); bundle.start(); // Access the Web Application String response = getHttpResponse("http://localhost:8090/mywebapp/foo"); assertEquals("ok", response);
This seemingly trivial code snippet has a number of issues that are probably worth looking into in more detail
The WAR might have missing or invalid web metadata (i.e. an invalid WEB-INF/web.xml descriptor)
The WAR Extender might not be present in the system
There might be multiple WAR Extenders present in the system
Code assumes that the endpoint is available on return of bundle.start()
Most Blueprint or WebApp bundles are not useful if their Blueprint/Web metadata is not processed. Even if they are processed but in the "wrong" order a user might see unexpected results (i.e. the webapp processes the first request before the underlying Blueprint app is wired together).
As a consequence the extender pattern is useful in some cases but not all. It is mainly useful if a bundle can optionally be extended in the true sense of the word.
Intercepting the Bundle Lifecycle
If the use case requires the notion of "interceptor" the extender pattern is less useful. The use case might be such that you would want to intercept the bundle lifecycle at various phases to do mandatory metadata processing.
An interceptor could be used for annotation processing, byte code weaving, and other non-optional/optional metadata processing steps. Typically interceptors have a relative order, can communicate with each other, veto progress, etc.
Lets look at how multiple interceptors can be used to create Web metadata and publish endpoints on the HttpService based on that metadata.
Here is how it works
The Wep Application processor registers two LifecycleInterceptors with the LifecycleInterceptorService
The Parser interceptor declares no required input and WebApp metadata as produced output
The Publisher interceptor declares WebApp metadata as required input
The LifecycleInterceptorService reorders all registered interceptors according to their input/output requirements and relative order
The WAR Bundle gets installed and started
The Framework calls the LifecycleInterceptorService prior to the actual state change
The LifecycleInterceptorService calls each interceptor in the chain
The Parser interceptor processes WEB-INF/web.xml in the invoke(int state, InvocationContext context) method and attaches WebApp metadata to the InvocationContext
The Publisher interceptor is only called when the InvocationContext has WebApp metadata attached. If so, it publishes the endpoint from the WebApp metadata
If no interceptor throws an Exception the Framework changes the Bundle state and fires the BundleEvent.
Client code is identical to above.
// Install and start the Web Application bundle Bundle bundle = context.installBundle("mywebapp.war"); bundle.start(); // Access the Web Application String response = getHttpResponse("http://localhost:8090/mywebapp/foo"); assertEquals("ok", response);
The behaviour of that code however, is not only different but also provides a more natural user experience.
Bundle.start() fails if WEB-INF/web.xml is invalid
An interceptor could fail if web.xml is not present
The Publisher interceptor could fail if the HttpService is not present
Multiple Parser interceptors would work mutually exclusiv on the presents of attached WebApp metadata
The endpoint is guaranteed to be available when Bundle.start() returns
The general idea is that each interceptor takes care of a particular aspect of processing during state changes. In the example above WebApp metadata might get provided by an interceptor that scans annotations or by another one that generates the metadata in memory. The Publisher interceptor would not know nor care who attached the WebApp metadata object, its task is to consume the WebApp metadata and publish endpoints from it.
For details on howto provide and register liefecycle interceptors have a look at the Lifecycle Interceptor Example.